using System;
using System.Collections.Generic;
using System.Text;
using System.Numerics;
using IWMAG.Scripting.Compiler.UserInfo;
using System.IO;

namespace IWMAG.Scripting.Compiler {
    public class Lexer {
        protected TokenMapping Tokens = new TokenMapping();
        protected string Raw;
        protected int Ptr;
        protected bool CommentMatching = true;
        internal int Line = 1;
        internal int Column = 0;
        private List<IUserInfo> ErrorList;

        protected char Peek {
            get {
                if (Ptr >= Raw.Length) {
                    return '\0';
                }
                return Raw[Ptr];
            }
        }

        public Lexer(string script, List<IUserInfo> ErrorList) {
            Raw = script.TrimEnd();
            Ptr = 0;
            this.ErrorList = ErrorList;
        }

        public IEnumerable<Lexeme> Lex() {
            Lexeme l = default(Lexeme);
            bool nolast = false;
            while (Ptr < Raw.Length) {
                bool matched = false;
                nolast = false;
                EatWhitespace();
                if (Match("//")) { // single line comment
                    Eat(); Eat();
                    while (Peek != '\n' && Peek != '\0') {
                        Eat();
                    }
                    EatWhitespace();
                    nolast = true;
                    continue;
                } else if (Match("/*")) { // block comment
                    Eat(); Eat();
                    while (Peek != '\0' && !Match("*/")) {
                        Eat();
                    }
                    if (Peek == '\0') {
                        ErrorList.Add(new CompileError(String.Format("Not closed block comment before EOF", Peek), Line, Column));
                        throw new LexException();
                    }
                    Eat(); Eat();
                    EatWhitespace();
                    nolast = true;
                    continue;
                } else {
                    foreach (KeyValuePair<string, Token> pair in Tokens) {
                        bool isalpha = char.IsLetter(pair.Key[0]);
                        bool matches = Match(pair.Key);
                        if (isalpha) {
                            if (Raw.Length > Ptr + pair.Key.Length) {
                                matches = matches && !char.IsLetterOrDigit(Raw[Ptr + pair.Key.Length]);
                            }
                        }
                        if (matches) {
                            l = MakeToken(pair.Value);
                            for (int i = 0; i < pair.Key.Length; i++) {
                                Eat();
                            }
                            matched = true;
                            break;
                        }
                    }
                }

                if (!matched) { // not a basic token
                    string match = "";
                    if (Char.IsNumber(Peek)) { // is a numeric constant
                        CommentMatching = false;
                        while (Char.IsNumber(Peek))
                            match += Eat();
                        if (Match(".") && !Match(1, ".")) {
                            match += Eat();
                            while (Char.IsNumber(Peek))
                                match += Eat();
                            // todo(sandy): make these not crash
                            l = MakeToken(Token.RealValue, Double.Parse(match));
                        } else {
                            int val;
                            if (int.TryParse(match, out val)) {
                                l = MakeToken(Token.IntValue, val);
                            } else {
                                ErrorList.Add(new CompileError("This ain't no big integer!!!", Line, Column - match.Length));
                                throw new LexException();
                            }
                        }
                        CommentMatching = true;
                        matched = true;
                    } else if (Peek == '"') { // is a string
                        CommentMatching = false;
                        Eat();
                        while (Peek != '"' && Peek != '\0') {
                            if (Match("\\")) { // escape sequence
                                Eat();
                                switch (Peek) {
                                    case 'n': match += '\n'; break;
                                    case 't': match += '\t'; break;
                                    case 'r': match += '\r'; break;
                                    case '"': match += '"'; break;
                                    case '0': match += '\0'; break;
                                }
                                Eat();
                            } else {
                                match += Eat();
                            }
                        }

                        if (Peek == '\0') {
                            ErrorList.Add(new CompileError2(Rules.UnTerminatedStringLiteral, Line, Column - match.Length));
                            //ErrorList.Add(new CompileError("Unterminated string literal", Line, Column - match.Length));
                            throw new LexException();
                        }
                        Eat();
                        CommentMatching = true;
                        l = MakeToken(Token.StringValue, match);
                        matched = true;
                    } else if (Char.IsLetter(Peek) || Peek == '_') { // is an identifier
                        match += Eat();
                        while (Char.IsLetter(Peek) || Char.IsNumber(Peek) || Peek == '_') {
                            match += Eat();
                        }
                        l = MakeToken(Token.Ident, match);
                        matched = true;
                    }
                } else if (Peek == '\0') {
                    goto end;
                }
                if (!matched) // not matched
                {
                    ErrorList.Add(new CompileError(String.Format("Unknown token `{0}`", Peek), Line, Column));
                    throw new LexException();
                }

                if (l.Type == Token.UnexpectedEndOfStream) {
                    yield break;
                } else {
                    EatWhitespace();
                    yield return l;
                }
            }

        end:
            if (l.Type != Token.UnexpectedEndOfStream && !nolast) {
                yield return l;
            }

            yield break;
        }

        public Lexeme MakeToken(Token t, object data = null) {
            if (data == null) {
                return new Lexeme(t, this);
            } else if (data is int) {
                return new Lexeme(t, this, (int)data);
            } else if (data is double || data is float) {
                return new Lexeme(t, this, (double)data);
            } else if (data is string) {
                return new Lexeme(t, this, (string)data);
            }

            throw new NotImplementedException("Not Yet Implemented");
        }

        protected bool Match(string s) {
            return Match(0, s);
        }

        protected bool Match(int rel, string s) {
            if (s.Length > Raw.Length - Ptr - rel) {
                return false;
            }
            return Raw.Substring(Ptr + rel, s.Length) == s;
        }

        protected char Eat() {
            if (Ptr >= Raw.Length) {
                return '\0';
            }

            char c = Raw[Ptr++];
            Column++;
            if (c == '\n') {
                Line++;
                Column = 0;
            }
            return c;
        }

        protected void EatWhitespace() {
            for (int i = Ptr; i < Raw.Length; i++)
                if (char.IsWhiteSpace(Peek)) {
                    Eat();
                } else {
                    break;
                }
        }
    }

    [Serializable]
    public class LexException : Exception
    {
        public LexException() { }
        public LexException(string message) : base(message) { }
        public LexException(string message, Exception inner) : base(message, inner) { }
        protected LexException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }
}